Redis基本操作——String

        关于Redis的命令,主要参考Redis的官方文档。这里主要像是翻译一下,并且给出一些实用的例子。排版上,按功能重新组织了一下指令,并将比较常用的指令放在了最前面。这里的Redis版本是3.0.7,应该是目前的最新版。

        注:redis的命令(SET,GET等)是不区分大小写的(KEY和VALUE区分的),为了方便所以操作可能都是小写的。

        指令清单:

一,SET的相关操作

1.SET key value [EX seconds] [PX milliseconds] [NX|XX]

        Set the string value of a key

        将键key设置成指定的字符串value。

        我们知道Redis是一种key-value数据库(当然这么说可能不够严谨,因为Redis支持很多高级的数据结构)。因此所有的数据都是通过key来访问,key就是键。

        SET指令可以将字符串的value和key绑定在一起。

        如果不加额外的参数。当key中已经保存了一个值的话,这个值会被覆盖成新的值,并且忽略掉原始类型(一个key对应的不一定是一个字符串,也可以是List等其他的数据结构);如果key不存在,那么则会在数据库中新增一个key,对应的值就是你刚刚设置的。

        例子:

1
2
3
4
5
6
7
8
9
10
redis> get name
(nil)
redis> set name miaoerduo
OK
redis> get name
"miaoerduo"
redis> set name miao
OK
redis> get name
"miao"

        GET命令用于查看key对应的值,我们下面会讲到。

        第一个get,由于name这个key不存在,因此我们得到的结果是nil,也就是说空。

        之后我们设置一次,然后就get到我们的结果了。之后再设置一次,会发现结果已经被覆盖。

        这就是set命令的基本用法。

        我们看到文档的后面有一些可选的参数。下面依次来介绍:

  • EX seconds:设置key的过时时间,单位为秒。
  • PX milliseconds:设置key的过期时间,单位为毫秒。

        以ex为例:

1
2
3
4
5
6
redis> set name miaoerduo ex 10
OK
redis> get name
"miaoerduo"
redis> get name
(nil)

        我们设置了name的过期时间为10秒。

        在设置完name之后,立刻获取name的值,可以得到正确的输出。但是过了10s之后,发现只能得到一个nil。说明这个key已经被清空了。

        那么这个设置有什么作用呢?

        我们说几个常见的应用场景:

  • 验证码

        我们经常在登录一些网站或是进行付款等操作的时候,都会收到一些验证码,并且说10min后失效。

        实际上就可以通过下面一条指令来实现:

1
set phone_num code ex 600

        用手机号作为key,验证码作为值,超时6min。这样当你输入好验证码,提交的时候,后台就可以了先get phone_num,再比较你的输入和数据库里面存的值,从而完成身份的验证。

  • session

        早前,网站通过cookie来保存用户的用户名和密码,之后出现了很多的安全隐患,因此就提出了session的机制。

        用户登陆成功之后,本地的cookie会保留一个较长的随机码,而网站后的后台则存储了这个随机码和你的用户id的对应关系。在你第二次登录的时候,cookie会传输到后台,而后台则根据你的随机码,获取你的用户信息,如果符合,则自动登录。这样,即使网站上有不法分子获取了你的cookie,也得不到你的任何信息,因为你的真实的有用的信息都存储在网站的后台。

        我们在登录邮箱的时候,通常都会有一个选项,7天内自动登录。这其实就是给后台存的session设置了一个超时。

        NX:(if Not eXist)只有键key不存在的时候才会设置key的值

        XX:只有键key存在的时候才会设置key的值

        NX通常用于实现锁机制,XX的功能,小喵暂时木有头绪。。。想到应用场景的筒子们可以留言告诉小喵,小喵看到了一定会更新的。

        举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
redis> del name
(integer) 0
redis> get name
(nil)
redis> set name miaoerduo nx
OK
redis> set name miaoerduo nx
(nil)
redis> get name
"miaoerduo"
redis> set name miao xx
OK
redis> get name
"miao"
redis> del name
(integer) 1
redis> set name miao xx
(nil)

        首先我们使用del指令删除了name(这个指令后面会介绍)。之后使用nx模式设置name,第一次成功了,而第二次就失败了,因为name已经存在了。之后使用xx模式修改,发现确实可以。del name之后,再使用xx模式,就失败了,因为此时的name已经不存在了。

        以上,就是set的所有的用法。

2.SETRANGE key offset value

        Overwrite part of a string at key starting at the specified offset

        这个命令可以覆盖掉key对应的string的一部分。offset表示需要覆盖的字符串的起始位置,之后会用value的值,覆盖掉原始string的对应位置的数据。

        这里有一些比较有意思的操作:如果原始key不存在,则默认为一个长度为0的字符串。如果offset超过原始string的长度,那么会在之前的string后面补充0以达到offset。如果value的长度超过了原始string后面可以覆盖的部分,则Redis内部会重新申请内存,完成数据的追加(还记得上一章的内容吗?),这时候数据库由于需要分配内存,可能会出现阻塞(需要分配的内存越大,阻塞时间越长)。

        例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
redis> set str "hello world"
OK
redis> setrange str 6 redis
(integer) 11
redis> get str
"hello redis"
redis> setrange str 15 aha
(integer) 18
redis> get str
"hello redis\x00\x00\x00\x00aha"
redis> del str
(integer) 1
redis> setrange str 5 "hello miao"
(integer) 15
redis> get str
"\x00\x00\x00\x00\x00hello miao"

        在这个实验中,我们先新建了一个str,内容是”hello world”,之后,从位置6开始写入字符串”redis”,则得到了”hello redis”。之后我们在15的位置,写入”aha”,这是offset已经比字符串的长度要大了,则Redis会默认填充0(\x00是0的16进制表达),之后再追加字符串。最后,我们给一个不存在的key使用setrange设置了一个值,结果表现得和空字符串一样。

3.MSET key value [key value …]

        Set multiple keys to multiple values

        一次性设置多个key-value。如果key的值已存在,则会直接覆盖。

        相当与同时调用多次SET命令。不过要注意的是,这个操作是原子的。也就是说,所有的keys都一次性设置的。如果你同时运行两个MSET来设置相同的keys,那么操作的结果也只会是两次MSET中的后一次的结果,而不会是混杂的结果。

        例子:

1
2
3
4
5
6
7
8
redis> mset name1 miaoerduo name2 miao name3 love
OK
redis> get name1
"miaoerduo"
redis> get name2
"miao"
redis> get name3
"love"

4.MSETNX key value [key value …]

        Set multiple keys to multiple values, only if none of the keys exist

        一次性设置多个key-value。如果存在任何一个key已经存在,那么这个操作都不会执行。所以,当使用MSETNX的时候,要么全部key都被修改,要么全部都不被修改。

        当然这个操作也是原子的。

1
2
3
4
5
6
7
8
9
10
11
12
redis> get name3
"love"
redis> msetnx name3 miaoerduo name4 miao
(integer) 0
redis> get name4
(nil)
redis> msetnx name4 miaoerduo name5 miao
(integer) 1
redis> get name4
"miaoerduo"
redis> get name5
"miao"

        当name3已经存在的时候,我们设置name3和name4,发现,连name4都没有创建。当设置name4和name5的时候,由于这两个key之前都不存在,因此设置成功了。

5.SETEX key seconds value

        Set the value and expiration of a key

        设置key的值和超时。和前面的set key value ex seconds一样。

6.PSETEX key milliseconds value

        Set the value and expiration in milliseconds of a key

        设置key的值和超时。和前面的set key value px milliseconds一样。

7.SETNX key value

        Set the value of a key, only if the key does not exist

        当key不存在的时候,设置key的值。和set key value nx一样。

二、GET的相关操作

1.GET key

        Get the value of a key

        根据给定的key,获取value值。这个操作,我们之前已经使用了很多次。

        如果key不存在,会返回nil。如果key对应的值不是string(List,Set等),则会报错,因为GET只能处理string类型的value。

        演示如下:

1
2
3
4
5
6
7
8
9
10
redis> get name
(nil)
redis> set name "miaoerduo"
OK
redis> get name
"miaoerduo"
redis> lpush arr 1 2 3 4 5
(integer) 5
redis> get arr
(error) WRONGTYPE Operation against a key holding the wrong kind of value

        注意,lpush是List部分的内容,arr的值是一个List的结构。这里只需要知道key不是 string类型的时候,get的时候就会报错。

2.MGET key [key …]

        Get the values of all the given keys

        还记得我们之前可以同时设置多个key-value吗?其实我们也可以一次性获取多个key的值。如果key不存在,则对应的地方返回nil。

        那么一次性获取多个值和单独一次一次GET有什么区别呢?

        主要有两点,原子操作和查询效率。

        比如博客上会统计评论数和访问数。如果我们依次读取这两部分的值,那么可能得到的两个值并不是同一时刻的。而如果使用MGET,则得到的一定是同一时刻的。这就是原子操作的威力(注,官方文档只介绍了SET和GET的操作是原子的,并没有说MGET是不是,这里小喵果断的说MGET是原子操作也是不合理的。但考虑到使用MGET的时候,是把查询指令一次性传输到后台来执行,所以应该原子操作。无论如何,在上面的例子中,使用MGET总是比两次GET要合理的)。

        另外,如果数据库的查询,都分为三个过程,传输查询指令,执行指令,输出结果。
如果分多次GET的话,在传输指令和输出结果的这两个部分就要重复很多次,效率大打折扣。

        分多次查询,需要传输的指令数目,也不会比单次多很多,为什么一定会影响效率呢?

        其实不然,传输数据需要很多的准备工作,不仅仅是数据的具体传输,有时候需要考虑连接的创建和关闭、设置锁等的开销。

1
2
3
4
5
6
7
8
redis> set key1 a
OK
redis> set key2 b
OK
redis> mget key1 key2 key3
1) "a"
2) "b"
3) (nil)

        我们只创建了key1和key2,使用MGET获取的时候,由于没有key3,对应的位置返回了nil。

3.GETRANGE key start end

        Get a substring of the string stored at a key

        该指令只要用于获取字符串的字串,在Redis2.0版本之前,叫做SUBSTR。strat和end是字串的起始和结束的位置,可以用负数来表示距离string尾部的未知的下标。-1是最后一个字符,-2是底数第二个字符。这个表达方式和Python的获取list的子list非常相似。

        需要注意的有两点:

  • 字串包括了start和end这两个位置的字符。在Python中是不包含end的。
  • 当给出的start和end超出了string的范围时,指令只会返回在string内的结果。
1
2
3
4
5
6
7
8
9
10
redis> set str "hello world"
OK
redis> getrange str 6 -1
"world"
redis> getrange str -5 -1
"world"
redis> getrange str 0 4
"hello"
redis> getrange str 0 100
"hello world"

        上述例子是几种情况下的输出。

4.GETSET key value

        Set the string value of a key and return its old value

        设置key的值,并返回之前的值。如果之前key的值不是string,则会报错。

        这个指令相当于先GET,再SET。

        这个指令可以用来配合INCR指令一起使用支持重置的技术功能(INCR我们后面会讲到)。先设置count为0,每次INCR使得count加1。等到需要获取计数的时候,使用GETSET count 0,就能获取计数的值,并且把计数器重置了。

1
2
3
4
5
6
redis> set str "hello"
OK
redis> getset str "world"
"hello"
redis> get str
"world"
  • 第一行设置str为”hello”
  • 第二行获取了str原先的值,并把str设置成”world”。
  • 第三行GET的时候,就是修改之后的值了。

三、string的修改操作

1.STRLEN key

        Get the length of the value stored in a key

        返回key对应的string的长度,如果key对应的不是string,则报错。如果key不存在,则返回0(还是把key对应的看成空字符串)。

1
2
3
4
5
6
7
8
redis> set str "hello world"
OK
redis> strlen str
(integer) 11
redis> get nokey
(nil)
redis> strlen nokey
(integer) 0

2.APPEND key value

        Append a value to a key

        如果key已经存在,且值为string,则将value追加到值的后面,如果key不存在,则会创建一个空的字符串的key,然后执行追加操作。

1
2
3
4
5
6
7
8
9
10
11
12
redis> set str "hello"
OK
redis> append str " world"
(integer) 11
redis> get str
"hello world"
redis> del str
(integer) 1
redis> append str "hello"
(integer) 5
redis> get str
"hello"

        这个例子中,我们先向已有值的str中append了一个字符串。然后向不存在的key,也添加了字符串。

3.INCR key

        Increment the integer value of a key by one

        对存储在key的整数值进行原子的加1操作。

        如果key不存在,则会设置默认值0,再加1。

        如果key存在,但是存储的值不是字符串,或者存储的字符串不能表示整数,则执行该操作时,会报错。

        这个操作仅限于64位的有符号的整型数据。

        比较有意思的是,虽然这个key存储的值是个字符串,但是该操作的效果和对相应的数字进行操作一样。而且,Redis中,在存储这类字符串的时候,底层上其实存储的就是一个整数,因此不存在存储上的浪费。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
redis> set count 0
OK
redis> incr count
(integer) 1
redis> incr count
(integer) 2
redis> del count
(integer) 1
redis> get count
(nil)
redis> incr count
(integer) 1
redis> incr count
(integer) 2

        值得注意的是,该操作是原子操作,即使有多个请求传输到Redis,count执行的结果都不会错误,所以我们可以大胆放心的用这个功能实现多线程的计数功能。

4.DECR key

        Decrement the integer value of a key by one

        对存储在key的整数值进行原子的减1操作。

        注意事项和INCR一样。

5.INCRBY key increment

        Increment the integer value of a key by the given amount

        对存储在key的整数值进行原子的加操作,加increment。

        如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成数字的字符串,就返回错误。这个操作最多支持64位有符号的整型数字。基本上和INCR一样。

6.DECRBY key decrement

        Decrement the integer value of a key by the given number

        对存储在key的整数值进行原子的减操作,减increment。

7.INCRBYFLOAT key increment

        Increment the float value of a key by the given amount

        对存储造key中的浮点数进行原子的加操作,加increment。

        如果key不存在,操作之前,key就会被置为0。如果key的value类型错误或者是个不能表示成浮点数的字符串,就返回错误。

        我们并没有DECRBYFLOAT这个操作,因此想要实现减操作,只需要把increment设成负的就可以。

1
2
3
4
5
6
7
8
9
10
redis> set a 1.5
OK
redis> incrbyfloat a 10.1
"11.6"
redis> incrbyfloat a 10.1
"21.7"
redis> incrbyfloat a -10.1
"11.6"
redis> incrbyfloat a -1.5e2
"-138.39999999999999999"

        浮点数可以用一般的小数和科学计数法表示。

四、二进制操作

1.SETBIT key offset value

        Sets or clears the bit at offset in the string value stored at key

        设置或者清空key的value(字符串)在offset处的bit值。这里将string看成由bit组成的数组。

        指定位置的bit可以被设置,或者被清空,这个由value(只能是0或者1)来决定。当key不存在的时候,就创建一个新的字符串value。要确保这个字符串足够长到在offset处有bit值。参数offset需要大于等于0,并且小于2^32(限制bitmap大小为512)。当key对应的字符串增大的时候,新增的部分bit值都是设置为0。

        该操作返回value原来的offset位置的bit值。

1
2
3
4
5
6
7
8
9
10
redis> setbit a 0 1
(integer) 0
redis> setbit a 1 1
(integer) 0
redis> setbit a 2 1
(integer) 0
redis> setbit a 3 1
(integer) 0
redis> get a
"\xf0"

        a最开始不存在,使用setbit操作,将a的前4位都设置成1。最终就得到了\xf0,这是16进制表示的结果,前4位都是1,其他都是0。

2.GETBIT key offset

        Returns the bit value at offset in the string value stored at key

        获取key对应的string在offset处的bit值。当offset超出了字符串长度的时候,这个字符串就被假定为由0比特填充的连续空间。当key不存在的时候,它就认为是一个空字符串,所以offset总是超出范围,然后value也被认为是由0比特填充的连续空间。

1
2
3
4
5
6
7
8
redis> setbit a 7 1
(integer) 0
redis> getbit a 0
(integer) 0
redis> getbit a 7
(integer) 1
redis> getbit a 100
(integer) 0

3.BITCOUNT key [start end]

        Count set bits in a string

        统计key的string的二进制中1的个数。

        start和end分别表示string的起始和结束位置,含义和GETRANGE中一样。

1
2
3
4
5
6
7
8
redis> setbit mykey 0 1
(integer) 0
redis> setbit mykey 10 1
(integer) 0
redis> setbit mykey 5 1
(integer) 0
redis> bitcount mykey
(integer) 3

4.BITOP operation destkey key [key …]

        Perform bitwise operations between strings

        对string进行bit级别的操作。具体操作有4种。AND,OR,XOR,NOT。

        用法如下:

1
2
3
4
BITOP AND destkey srckey1 srckey2 srckey3 … srckeyN
BITOP OR destkey srckey1 srckey2 srckey3 … srckeyN
BITOP XOR destkey srckey1 srckey2 srckey3 … srckeyN
BITOP NOT destkey srckey

        NOT操作后面只有一个目标key和srckey,是因为NOT操作是一元的。

        对于AND,OR和XOR操作,Redis会将srckey1,srckey2,…,srckeyN这些字符串对位进行相关操作,之后将结果存入destkey中。

        如果srckey的length不相等的话,Redis内部会将短的字符串补齐,并填充上0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
redis> set key1 "\xf0\xf0"
OK
redis> set key2 "\x0f\x0f"
OK
redis> set key3 "\xff\xff"
OK
redis> bitop and destkey key1 key2 key3
(integer) 2
redis> get destkey
"\x00\x00"
redis> bitop or destkey key1 key2 key3
(integer) 2
redis> get destkey
"\xff\xff"
redis> bitop xor destkey key1 key2 key3
(integer) 2
redis> get destkey
"\x00\x00"
redis> bitop not destkey key1
(integer) 2
redis> get destkey
"\x0f\x0f"

5.BITPOS key bit [start] [end]

        Find first bit set or clear in a string

        返回string的二进制中第一个0或1的位置。

        这里将string看成一个有许多bit组成的数组。其中start和end表示string的一个间隔,如果指定了start和end,则BITPOS只会查询这个区间。注意,start和end表示的字符的位置,不是bit的位置。

        即使指定了start和end,BITPOS这个操作也只会这个目标bit的绝对地址。

        有几点需要注意:

        在没有指定查询区间或只指定start的时候,查询bit位为0的位置时,如果string中没有该位,则会返回string的bit位的总数。比如在\xff\xff\xff直接查询bit为0的位置,Redis默认该字符串后面都是0,因此,返回的结果就是12(下标从0开始数的)。

        如果指定了查询区间,无论查询0或是1,在没查询到的时候只会返回-1。

        在没有指定查询区间时,查询bit位为1的位置时,如果string中没有该位,则会返回-1,表示未查询到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
redis> set bits "\x00\xff\x00\xff"
OK
redis> strlen bits
(integer) 4
redis> bitpos bits 0
(integer) 0
redis> bitpos bits 1
(integer) 8
redis> bitpos bits 0 1
(integer) 16
redis> bitpos bits 0 1 -1
(integer) 16
redis> bitpos bits 1 1 -1
(integer) 8
redis> bitpos bits 0 3
(integer) 32
redis> bitpos bits 0 3 3
(integer) -1

        看上面的例子,bits的初始设置的二进制表示为:

1
00000000 11111111 00000000 11111111

        直接获取0(bitpos bits 0)的位置为0,获取1(bitpos bits 1)的位置为8。

        指定开始位置为1(start = 1)的时候,0第一次出现(bitpos bits 1)的位置为 16。

        指定起始位置为3(start = 3)的时候,0第一次出现(bitpos bits 0 3)的位置为32。这是因为Redis在查询字符串的时候查询到了字符串的末尾,之后默认末尾的后面都是 0,因此得到了32这的个位置。

        最后,当指定必须在第3个字节(从0开始数)查询0的时候,由于查不到0,因此只返回了-1。